//
// Created by entaoyang@163.com on 2017/10/18.
// Copyright (c) 2017 yet.net. All rights reserved.
//

import Foundation
#if os(iOS) || os(tvOS)
import UIKit
#else
import AppKit
#endif


public class ConstraintStorage: NSObject {
    private let map: Map<ConstraintKey, NSLayoutConstraint> = [:]
    unowned let view: View

    fileprivate init(_ view: View) {
        self.view = view
        super.init()
        view.translatesAutoresizingMaskIntoConstraints = false
        view.autoresizesSubviews = false
    }

    public func clear() {
        for (_, c) in map {
            c.isActive = false
        }
        map.removeAll()
    }

    public func put(_ c: NSLayoutConstraint) {
        let k = c.key
        if let old = map.removeValue(forKey: k) {
            old.isActive = false
        }
        map[k] = c
        c.isActive = true
        if let view = c.firstItem as? View {
            view.postUpdateContraints()
            view.superview?.postUpdateContraints()
            view.setNeedsDisplay()
        }
    }

    public func find(ident: String) -> NSLayoutConstraint? {
        map.values.first(where: { $0.identifier == ident })
    }

    public func remove(ident: String) {
        if let c = find(ident: ident) {
            c.isActive = false
            self.map.remove(key: c.key)
        }
    }

    public func update(ident: String, constant: CGFloat) {
        if let c = find(ident: ident) {
            if abs(c.constant - constant) > 0.2 {
                c.constant = constant
                view.setNeedsUpdateConstraints()
            }
        }
    }

    public func get(attr: ConstraintAttr, rel: ConstraintRel) -> NSLayoutConstraint? {
        map[ConstraintKey(attr: attr, rel: rel)]
    }

    public func eq(attr: ConstraintAttr, constant: CGFloat) {
        put(NSLayoutConstraint(item: view, attribute: attr, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: constant))
    }

    public func eq(parent attr: ConstraintAttr, value: CGFloat) {
        put(NSLayoutConstraint(item: view, attribute: attr, relatedBy: .equal, toItem: view.superview, attribute: attr, multiplier: 1, constant: value))
    }

    public func eq(other: View, attr: ConstraintAttr, value: CGFloat) {
        put(NSLayoutConstraint(item: view, attribute: attr, relatedBy: .equal, toItem: other, attribute: attr, multiplier: 1, constant: value))
    }

    func updateConstant(attr: ConstraintAttr, rel: ConstraintRel, constant: CGFloat) {
        if let c = get(attr: attr, rel: rel) {
            c.constant = constant
            self.view.setNeedsUpdateConstraints()
            self.view.superview?.setNeedsUpdateConstraints()
        } else {
            loge("updateConstant: update nothing ")
        }
    }

    private func update(_ attr: LayoutAttribute, _ value: CGFloat) -> Bool {
        let k = ConstraintKey(attr: attr, rel: .equal)
        switch attr {
        case .left, .top:
            if map[k]?.constant != value {
                eq(parent: attr, value: value)
                return true
            }
        case .width, .height:
            if map[k]?.constant != value {
                eq(attr: attr, constant: value)
                return true
            }
        default:
            break
        }
        return false
    }

    public func updateFrame(_ rect: Rect) {
        var changed = false
        if view.superview != nil {
            changed = changed || update(.left, rect.minX)
            changed = changed || update(.top, rect.minY)
        }
        changed = changed || update(.width, rect.width)
        changed = changed || update(.height, rect.height)
        if changed {
            Task.foreDelay(seconds: 0) { [weak self] in
                self?.view.superview?.postLayout()
            }
        }
    }
}

public extension View {
    var constraintStorage: ConstraintStorage {
        ensureAttr(key: "constraintStorage") {
            ConstraintStorage(self)
        }
    }
}
